home *** CD-ROM | disk | FTP | other *** search
/ HPAVC / HPAVC CD-ROM.iso / WINER.ZIP / CHAP10.TXT < prev    next >
Text File  |  1994-09-04  |  52KB  |  1,190 lines

  1.                                CHAPTER 10
  2.  
  3.                        KEY MEMORY AREAS IN THE PC
  4.  
  5.  
  6. Two very important BASIC keywords that are sadly neglected by many
  7. programmers are PEEK and POKE.  Most people understand that these let you
  8. read from and write to memory locations.  But what are they really good
  9. for?  The whole point of a high-level language like BASIC is to avoid such
  10. direct memory access, and to many programmers these commands may seem like
  11. an enigma.
  12.    In most cases, you *don't* need to access memory with PEEK and POKE. 
  13. Unlike C and assembly language that require direct memory operations to
  14. process strings and arrays, BASIC includes a full complement of commands
  15. for this.  However, there is at least one important use for PEEK and POKE
  16. that cannot be accomplished in any other way: accessing low memory.
  17.    The portion of memory in every PC that begins at Hex address 0000:0400
  18. is called the *BIOS Data Area*, and it contains much useful information.
  19. For example, the equipment word at address &H410 tells how many diskette
  20. drives are installed, and how many parallel and serial ports there are. 
  21. The keyboard status flags at address &H417 can be read (and written), to
  22. reflect whether the Caps Lock and NumLock states are active.
  23.    In this chapter I will describe all of the low memory locations that are
  24. relevant to a BASIC program, and present numerous practical examples to
  25. show how this data can be utilized.  This is by no means a complete list
  26. of every BIOS data address that is available in the PC.  Rather, I have
  27. purposely limited it to those that I have found useful.
  28.  
  29.  
  30. IMPROVING PEEK AND POKE
  31. =======================
  32.  
  33. One potential limitation that needs to be addressed first is how to access
  34. full words of data.  BASIC's PEEK and POKE operate on single bytes only,
  35. and reading or writing two bytes at a time is a messy proposition at best.
  36.    Chapter 9 introduced a pair of routines called PeekWord and PokeWord,
  37. that allowed accessing memory a word at a time.  In the context those were
  38. presented, a fair amount of code could be saved by consolidating the
  39. necessary code into a subprogram or function.  But in the interest of speed
  40. and even further code size reductions, the following assembly language
  41. routines are better still.
  42.  
  43. ;PEEKPOKE.ASM, simplifies access to full words
  44.  
  45. .Model Medium, Basic
  46. .Code
  47.  
  48. PeekWord Proc Uses ES, SegAddr:DWord
  49.   Les  BX,SegAddr     ;load the segment and address
  50.   Mov  AX,ES:[BX]     ;read the word into AX
  51.   Ret                 ;return to BASIC
  52. PeekWord Endp
  53.  
  54.  
  55. PokeWord Proc Uses ES, SegAddr:DWord, Value:Word
  56.   Les  BX,SegAddr     ;load the segment and address
  57.   Mov  AX,Value       ;and the new value to store there
  58.   Mov  ES:[BX],AX     ;write the value into memory
  59.   Ret                 ;return to BASIC
  60. PokeWord Endp
  61. End
  62.  
  63. Both of these routines expect the parameters to be passed by value, for
  64. faster speed and smaller code.  Therefore, you will declare them as
  65. follows:
  66.  
  67.    DECLARE FUNCTION PeekWord%(BYVAL Segment%, BYVAL Address%)
  68.    DECLARE SUB PokeWord(BYVAL Segment%, BYVAL Address%, BYVAL Value%)
  69.  
  70. Then to read a word of memory--say, the address of the LPT1 printer adapter
  71. at address &H408--PeekWord would be invoked like this:
  72.  
  73.    LPT1Addr% = PeekWord%(0, &H408)
  74.  
  75. And to write the letter "A" in the lower left corner of a color display
  76. screen in white on blue you could use PokeWord, thus:
  77.  
  78.    CALL PokeWord(&HB800, 3998, &H1741)
  79.  
  80. Notice that PeekWord returns a negative value for numbers greater than
  81. 32767.  This is normal, as explained in Chapter 2.  However, the same
  82. negative value that PeekWord returns can be used as an argument to PokeWord
  83. with the correct results.
  84.  
  85.  
  86. LOW MEMORY ADDRESSES
  87. ====================
  88.  
  89. The sections that follow are organized by category, since this is how low
  90. memory is arranged in the PC.  That is, one section discusses the RS-232
  91. communications data area, the next shows the portion of memory used by the
  92. printer adapters, and so forth.  Each address is listed in ascending order;
  93. by convention, Hex notation is used exclusively for these addresses.  In
  94. all of the examples shown here, you will use a segment value of zero.
  95.    It is important to understand that besides memory addresses that are
  96. accessed with PEEK and POKE (or in this case their full-word equivalents),
  97. the IBM PC family also has a series of input and output ports.  These ports
  98. are accessed using INP and OUT commands instead of PEEK and POKE.  I
  99. mention this here because ports are referred to in several places in the
  100. discussions that follow.  In particular, the communications ports that are
  101. exchanged in the next section are in fact port numbers, and not memory
  102. addresses.  Some useful port numbers are given at the end of this chapter,
  103. along with code examples that show how to read from and write to them.
  104.    Table 10-1 provides a summary of all the low memory addresses that are
  105. described in this chapter.
  106.  
  107. Address   Meaning
  108. =======   ==========================================
  109.  &H400    2 bytes, COM1 port number
  110.  &H402    2 bytes, COM2 port number
  111.  &H404    2 bytes, COM3 port number
  112.  &H406    2 bytes, COM4 port number
  113.  
  114.  &H408    2 bytes, LPT1 port number
  115.  &H40A    2 bytes, LPT2 port number
  116.  &H40C    2 bytes, LPT3 port number
  117.  &H40E    2 bytes, LPT4 port number
  118.  
  119.  &H410    2 bytes, Equipment List
  120.  &H413    2 bytes, installed memory (K)
  121.  &H417    2 bytes, keyboard status
  122.  &H418    2 bytes, enhanced keyboard status
  123.  
  124.  &H41A    2 bytes, keyboard buffer head pointer
  125.  &H41C    2 bytes, keyboard buffer tail pointer
  126.  &H41E    30 bytes, keyboard buffer
  127.  
  128.  &H43F    1 byte, diskette motor on indicator
  129.  &H440    1 byte, diskette motor countdown timer
  130.  
  131.  &H449    1 byte, current video mode
  132.  &H44A    2 bytes, current screen width (columns)
  133.  &H44C    2 bytes, current video page size (bytes)
  134.  &H462    1 byte, current video page number
  135.  &H463    2 bytes, CRT controller port number
  136.  
  137.  &H46C    4 bytes, long integer system timer count
  138.  
  139.  &H478    4 bytes, LPT1 - LPT4 timeout values
  140.  
  141.  &H484    1 byte, EGA/VGA screen height (rows)
  142.  &H485    2 bytes, character height (scan lines)
  143.  &H487    1 byte, EGA/VGA Features bits
  144.  
  145.  &H4F0    16 bytes, Inter-Application Area
  146.  
  147.  &H500    1 byte, PrtSc busy flag
  148.  
  149.  &H504    1 byte, active drive for one-diskette PC
  150.  
  151. Table 10-1: Key low memory addresses in the PC.
  152.  
  153. COMMUNICATIONS PORT ADDRESSES
  154. =============================
  155.  
  156. The four words starting at address &H400 hold the port numbers for each
  157. installed RS-232 communications adapter.  For example, the port number for
  158. COM1 is contained in the word at address &H400, and the port number for
  159. COM3 is at address &H404.  Because these port numbers are words rather than
  160. bytes, the COM1 port number is contained in both &H400 and &H401.  Thus,
  161. COM2 starts at address &H402, and COM3 starts at &H404.
  162.    BASIC allows you to open only COM ports 1 and 2; however by exchanging
  163. these addresses you can substitute ports 3 and 4 if necessary.  The
  164. complete program that follows first swaps the port numbers for COM1 and
  165. COM3, and then opens COM1 for output.  Since the port numbers are swapped,
  166. it is actually COM3 that is being opened.
  167.  
  168.  
  169. DEFINT A-Z
  170. DECLARE FUNCTION PeekWord% (BYVAL Segment, BYVAL Address)
  171. DECLARE SUB PokeWord (BYVAL Segment, BYVAL Address, BYVAL Value)
  172.  
  173. COM1 = PeekWord%(0, &H400)    'save COM1 port number
  174. COM3 = PeekWord%(0, &H404)    'save COM3 port number
  175. CALL PokeWord(0, &H400, COM3) 'assign COM3 to COM1
  176. CALL PokeWord(0, &H404, COM1) 'and then COM1 to COM3
  177.  
  178. OPEN "COM1:1200,N,8,1,RS,DS" FOR RANDOM AS #1
  179. PRINT #1, "ATDT 1-555-1212"   'dial information
  180. CLOSE #1
  181.  
  182. CALL PokeWord(0, &H400, COM1) 'restore the original values
  183. CALL PokeWord(0, &H404, COM3)
  184.  
  185.  
  186. PRINTER PORT ADDRESSES
  187. ======================
  188.  
  189. The four printer port numbers start at address &H408, and they are similar
  190. to those used to hold the communications ports and may also be exchanged
  191. if necessary.  For example, if you have a program that uses LPRINT
  192. commands, all printed output will be sent to LPT1.  If at some later time
  193. you want to use the same program with LPT2, you can exchange the port
  194. numbers instead of having to rewrite the program.  A short code fragment
  195. that does this is shown following.
  196.  
  197.  
  198. DEFINT A-Z
  199. DECLARE FUNCTION PeekWord% (BYVAL Segment, BYVAL Address)
  200. DECLARE SUB PokeWord (BYVAL Segment, BYVAL Address, BYVAL Value) 
  201.  
  202. LPT1 = PeekWord%(0, &H408)    'save LPT1 port number
  203. LPT2 = PeekWord%(0, &H40A)    'save LPT2 port number
  204. CALL PokeWord(0, &H408, LPT2) 'assign LPT2 to LPT1
  205. CALL PokeWord(0, &H40A, LPT1) 'and LPT1 to LPT2
  206.  
  207. LPRINT "This is printed on LPT2"
  208. CALL PokeWord(0, &H408, LPT1) 'restore the original values
  209. CALL PokeWord(0, &H40A, LPT2)
  210. LPRINT "And now we're back to LPT1"    'prove it worked
  211.  
  212.  
  213. Like the communications port addresses, each printer port address is a
  214. full word, so while the first is located at address &H408, the second is
  215. at &H40A.  You will also find PeekWord useful because it does not require
  216. you to change the current DEF SEG setting.  Although there is no harm in
  217. assigning a new DEF SEG value in most cases, it is not easy to restore it
  218. to the original setting.  Therefore, when writing reusable subprograms and
  219. functions that need to access memory, you don't have to worry about
  220. affecting a subsequent PEEK or BLOAD in the main program.
  221.  
  222.  
  223. SYSTEM DATA
  224. ===========
  225.  
  226. One of the most valuable data items in low memory is the equipment list
  227. in the word starting at address &H410.  The information contained here is
  228. bit coded, to indicate which and how many peripherals are installed in the
  229. host PC.  Figure 10-1 shows the organization of this word.  Bits not
  230. identified are either reserved, or not particularly useful.
  231.  
  232.  
  233. 15 14 13 12 11 10 09 08 07 06 05 04 03 02 01 00 <- bit numbers
  234.  x  x -------------------------------------------- printers
  235.           x -------------------------------------- 1 = game port
  236.                 x  x  x -------------------------- serial ports
  237.                          x  x -------------------- diskette *
  238.                                x  x -------------- video mode
  239.                                           x ------ coprocessor
  240.                                               x -- diskette *
  241.  
  242. * If Bit 0 is set, then bits 6 and 7 together reflect the number of
  243. diskette drives *less one*.  If Bit 0 is clear then no diskette drives are
  244. installed.
  245.  
  246. Figure 10-1: The organization of the equipment list word at address &H410.
  247.  
  248.  
  249. Because the data in this word is bit coded, you must use AND to extract
  250. the necessary information.  For example, to see if a math coprocessor is
  251. installed you must turn off all but bit 1, and see if the result is zero
  252. or not:
  253.  
  254.    IF PeekWord%(0, &H410) AND 2 THEN
  255.      PRINT "A coprocessor is installed."
  256.    ELSE
  257.      PRINT "Sorry, no coprocessor detected."
  258.    END IF
  259.  
  260. This brings up an important point, because it is not immediately obvious
  261. what values you should use to isolate the various bits in a word.  It would
  262. be terrific if Microsoft BASIC offered the ability to handle binary values
  263. directly.  The Microsoft Macro Assembler allows this, as does PowerBasic. 
  264. In the absence of &B and a BIN$ function, the following short function can
  265. be used to determine the correct integer value for a given sequence of
  266. binary bits.
  267.  
  268.  
  269. FUNCTION Bin% (Bit$) STATIC
  270.   Temp& = 0
  271.   Length = LEN(Bit$)
  272.   FOR X = 1 TO Length
  273.     IF MID$(Bit$, Length - X + 1, 1) = "1" THEN
  274.       Temp& = Temp& + 2 ^ (X - 1)
  275.     END IF
  276.   NEXT
  277.   IF Temp& > 32767 THEN
  278.     Bin% = Temp& - 65536
  279.   ELSE
  280.     Bin% = Temp&
  281.   END IF
  282. END FUNCTION
  283.  
  284.  
  285. Given a string of binary digits of the form "01011001", the Bin function
  286. returns an equivalent integer value.  You could add this function to your
  287. programs, or use it to determine constant values ahead of time.  For
  288. example, to determine the number of diskette drives that are installed
  289. requires isolating bits 6 and 7.  This is simple in assembly language,
  290. where you can specify an AND mask using 11000000b as a value.  The example
  291. below obtains the equipment word, and then uses the Bin function to disable
  292. all but bits 6 and 7.
  293.  
  294.    Equipment = PeekWord%(0, &H410) 
  295.    Floppies = 1 + (Equipment AND Bin%("11000000")) \ 64 
  296.    PRINT Floppies; "diskette drive(s) installed" 
  297.  
  298. Although the Bin function is used in the code, I recommend that you create
  299. a simple test program first, to determine the value of 11000000 (192) once
  300. ahead of time.  Then, the Bin function can be omitted from the final
  301. program and the second line would be changed as follows:
  302.  
  303.    Floppies = 1 + (Equipment AND 192) \ 64
  304.  
  305. Notice the use of parentheses to force BASIC to combine Equipment and the
  306. number 192 before dividing by 64 with AND.  If these are omitted BASIC
  307. will instead combine Equipment with the result of 192 divided by 64, which
  308. is not correct.
  309.    One final technique you should understand is how to shift bits into the
  310. correct position to obtain the actual value the bits represent.  Treated
  311. as bits alone, the number of diskette drives is represented as 00, 01, 10,
  312. or 11, and the decimal equivalents for these binary numbers are 0, 1, 2,
  313. and 3.  But because of their positioning in the equipment word, the bits
  314. must be shifted to the right six places.  After all, the value 11000000
  315. (192) is certainly not the same as the value 11 (3).
  316.    This is handled simply and elegantly using integer division as shown. 
  317. To shift a number right one position divide it by 2; to shift right 2
  318. places divide by 4, and so forth.  Since the diskette bits need to be
  319. shifted six places, the equipment variable is divided by 64 after AND is
  320. used to mask off the unrelated bits.  Likewise, to shift bits left you can
  321. multiply by 2, 4, 8, and so forth.  The number to use when dividing or
  322. multiplying can also be determined by raising 2 to the number of bits
  323. power.  For example, to shift a number right five places you would divide
  324. by 2 ^ 5 = 32.
  325.    A problem arises when dealing with the highest order bit, because to
  326. BASIC this bit implies a negative number.  Therefore, when bit 15 is set,
  327. dividing will not produce the expected results.  One workaround that is
  328. admittedly clumsy is to test that bit explicitly, then mask it off and
  329. shift the bits as needed, and finally use an IF test to see if the bit had
  330. been set.  The only place this is necessary in the equipment list is when
  331. reading the number of parallel printers that are present.  The first
  332. example below reports the number of serial ports, and the second tells how
  333. many parallel ports are installed.
  334.  
  335.  
  336. Equipment = PeekWord%(0, &H410)
  337. Serial = (Equipment AND Bin%("11000000000")) \ 512
  338. PRINT Serial; "serial port(s) installed"
  339.  
  340.  
  341. IF Equipment AND Bin%("1000000000000000") THEN
  342.   HiBitSet = -1
  343. END IF
  344. Parallel = (Equipment AND Bin%("0100000000000000")) \ 16384
  345. IF HiBitSet THEN Parallel = Parallel + 2
  346. PRINT Parallel; "parallel port(s) installed"
  347.  
  348.  
  349. In the interest of completeness I should point out that it is not strictly
  350. necessary to manipulate bit 15 when accessing the equipment word.  Since
  351. none of the information straddles a byte boundary, BASIC's PEEK can in fact
  352. be used to read just the high byte.  Since a byte value is never higher
  353. than 255, the entire issue of saving and then masking that bit can be
  354. avoided.  But there are other situations you may encounter where an entire
  355. word must be processed and the highest bit may be set.
  356.    The final useful item in the equipment word is the initial video mode. 
  357. I've seen many programmers read use information to determine if a color
  358. or monochrome monitor is installed like this:
  359.  
  360.    DEF SEG = 0
  361.    IF (PEEK(&H410) AND &H30) = &H30 THEN
  362.      ' monochrome
  363.    ELSE
  364.      ' color
  365.    END IF
  366.  
  367. There are two problems with this approach.  The most serious is that this
  368. reflects the monitor that was active when the PC was first powered up. 
  369. These days, many people have two monitors connected to their PC, and you
  370. usually need to know which is currently active.  The other problem is this
  371. requires more code than the better method I showed in Chapter 6 which reads
  372. the port address of the currently active video adapter:
  373.  
  374.    DEF SEG = 0
  375.    IF PEEK(&H463) = &HB4 THEN
  376.      ' monochrome
  377.    ELSE
  378.      ' color
  379.    END IF
  380.  
  381. Besides the equipment word at address &H410, another word at address &H413
  382. holds the amount of memory that is installed in KiloBytes.  Note that this
  383. word does not reflect any extended or expanded memory that may be present. 
  384. Also note that a much better indicator of how much memory is actually
  385. available to a program is BASIC's FRE(-1) function.  The short code
  386. fragment below shows how to determine the total DOS-accessible memory that
  387. is installed.
  388.  
  389.    TotalK = PeekWord%(0, &H413)
  390.    PRINT TotalK; "K Bytes present in this PC."
  391.  
  392.  
  393. KEYBOARD DATA
  394. =============
  395.  
  396. As with the equipment word, the keyboard data area also maintains bit-coded
  397. information.  However, this word indicates the setting of the various
  398. keyboard shift states.  Unlike many of the other addresses in the BIOS data
  399. area, some of these bits may be written to as well as read from.
  400.    The byte at address &H417 shows the current status of all of the shift
  401. keys, and the lower four bits may be either read or written.  The remaining
  402. bits in this byte should not be written to, nor should you alter any of the
  403. bits in the next byte at address &H418.  Figure 10-2 shows the meaning of
  404. each bit in the byte at address &H417, and Figure 10-3 shows the bits at
  405. address &H418 that relate to extended keyboards only.
  406.  
  407. 7   6   5   4   3   2   1   0   <-- bits
  408.  
  409. x --------------------------------- Insert state
  410.     x ----------------------------- Caps Lock
  411.         x ------------------------- Num Lock
  412.             x --------------------- Scroll Lock
  413.                 x ----------------- Alt key
  414.                     x ------------- Ctrl key
  415.                         x --------- Left Shift key
  416.                             x ----- Right Shift key
  417.  
  418. Figure 10-2: The organization of the keyboard data byte at address &H417.
  419.  
  420.  
  421.  
  422.  
  423.  
  424. 7   6   5   4   3   2   1   0   <-- bits
  425.  
  426. x --------------------------------- Insert
  427.     x ----------------------------- Caps Lock
  428.         x ------------------------- Num Lock
  429.             x --------------------- Scroll Lock
  430.                 x ----------------- Pause state
  431.                     x ------------- Sys Req
  432.                         x --------- Left Alt key
  433.                             x ----- Left Ctrl key
  434.  
  435. Figure 10-3: The organization of the extended keyboard data byte at address
  436. &H418.
  437.  
  438. The various flags in the upper four bits at address &H417 are toggled on
  439. and off by the BIOS each time the corresponding keys are pressed.  For
  440. example, bit 6 is set while the Caps Lock is active, and bit 5 is clear
  441. when Num Lock is not in effect.  Note, however, that the Insert flag is of
  442. no practical use, and you should not rely on that bit in your programs. 
  443. If you are writing an input routine (or using the one shown in Chapter 6)
  444. you should keep track of the insert status manually.
  445.    The lower four bits indicate the current state of the various shift
  446. keys, and they are set only while the associated key is actually being
  447. pressed.  Bits in the next word at address &H418 let you determine which
  448. Alt and Ctrl keys are pressed, for keyboards that have more than one of
  449. those keys.  In most cases you will probably just want to know if these
  450. keys are active, and not distinguish between the left and the right key. 
  451. Therefore, you will usually ignore the extended keyboard information,
  452. unless you need to detect the SysReq key.
  453.    As with the equipment list, you will use a combination of PeekWord (or
  454. PEEK) to read all of the flags, and then use AND to isolate just those bits
  455. you care about.  Because there is only one bit that corresponds to each
  456. keyboard state flag, it is not necessary to divide or multiply to convert
  457. multiple bits into a number.
  458.    The examples below show how to test each of the bits in the byte at
  459. address &H417, without regard to the extra Ctrl and Alt key information
  460. contained at address &H418.
  461.  
  462. CLS
  463. PRINT "Press the various Shift and Lock keys, ";
  464. PRINT "then press Escape to end this madness."
  465. COLOR 0, 7
  466.  
  467. DO
  468.   Status = PeekWord%(0, &H417)
  469.  
  470.   LOCATE 10, 1
  471.   IF Status AND 1 THEN
  472.     PRINT "RightShift"
  473.   ELSE
  474.     GOSUB ClearIt
  475.   END IF
  476.  
  477.   LOCATE 10, 11
  478.   IF Status AND 2 THEN
  479.     PRINT "Left Shift"
  480.   ELSE
  481.     GOSUB ClearIt
  482.   END IF
  483.    
  484.   LOCATE 10, 21
  485.   IF Status AND 4 THEN
  486.     PRINT "Ctrl key"
  487.   ELSE
  488.     GOSUB ClearIt
  489.   END IF
  490.  
  491.   LOCATE 10, 31
  492.   IF Status AND 8 THEN
  493.     PRINT "Alt key"
  494.   ELSE
  495.     GOSUB ClearIt
  496.   END IF
  497.   
  498.   LOCATE 10, 41
  499.   IF Status AND 16 THEN
  500.     PRINT "ScrollLock"
  501.   ELSE
  502.     GOSUB ClearIt
  503.   END IF
  504.  
  505.   LOCATE 10, 51
  506.   IF Status AND 32 THEN
  507.     PRINT "Num Lock"
  508.   ELSE
  509.     GOSUB ClearIt
  510.   END IF
  511.  
  512.   LOCATE 10, 61
  513.   IF Status AND 64 THEN
  514.     PRINT "Caps Lock"
  515.   ELSE
  516.     GOSUB ClearIt
  517.   END IF
  518.    
  519.   LOCATE 10, 71
  520.   IF Status AND 128 THEN
  521.     PRINT "Insert"
  522.   ELSE
  523.     GOSUB ClearIt
  524.   END IF
  525.  
  526. LOOP UNTIL INKEY$ = CHR$(27)
  527. COLOR 7, 0
  528. END
  529.  
  530. ClearIt:
  531.   COLOR 7, 0
  532.   PRINT SPACE$(10);
  533.   COLOR 0, 7
  534.   RETURN
  535.  
  536. As you can see, to read a single bit you use AND to isolate it from the
  537. rest, and then test if the result is non-zero.  Setting a bit requires
  538. slightly more work, because it is important not to disturb the other bits
  539. in that byte.  This requires that you first read the current information,
  540. change only the bit or bits of interest, and then write the modified data
  541. back to the same location.  The next short example shows how to turn the
  542. CapsLock state on and then off again.
  543.  
  544.  
  545. CurStatus = PeekWord%(0, &H417)
  546. NewStatus = CurStatus OR Bin%("1000000")
  547. CALL PokeWord(0, &H417, NewStatus)
  548.  
  549. PRINT "Press a key to turn off CapsLock"
  550. WHILE INKEY$ = "": WEND
  551.  
  552. NewStatus = NewStatus AND Bin%("10111111")
  553. CALL PokeWord(0, &H417, NewStatus)
  554.  
  555.  
  556. Notice the difference between how OR is used in the first example, and how
  557. AND is used in the second one.  In the first case we want to set a bit,
  558. so only that bit is specified in the binary mask.  The remaining bits stay
  559. the same as they were--if they are already set then OR will leave them that
  560. way.  But to turn off the CapsLock bit requires that all of the mask bits
  561. be set *except* the one you wish to force off.  Other bits that were
  562. already on will remain on after being combined with AND and 1.
  563.  
  564.  
  565. THE KEYBOARD BUFFER
  566.  
  567. The next group of low memory keyboard addresses relate to the keyboard
  568. buffer.  As you undoubtedly know, every PC has a keyboard buffer that can
  569. hold up to fifteen keystrokes.  When a program is off doing something and
  570. is unable to read the keyboard, the BIOS keyboard routines will store keys
  571. that have been typed.  Then, when the program finally gets around to
  572. reading the keyboard, they are waiting there to be read.  The keyboard
  573. buffer is therefore also called the *type-ahead* buffer.
  574.    A series of 34 bytes are set aside for the keyboard buffer.  Two words
  575. (four bytes) are used to hold the current head and tail pointers that show
  576. where the next key will be read from, and where the next will be stored. 
  577. The current head address is stored at address &H41A and the tail at address
  578. &H41C.  Thirty additional bytes are used to store the actual keystrokes,
  579. with two bytes used for each.  The keyboard buffer is called a *circular
  580. buffer*, because the start and end points are constantly revolving.
  581.    When a PC is first powered up, the head of the buffer holds the address
  582. &H41E, which is the start of the buffer memory area.  The tail is also
  583. initially set to that same address, until a key is pressed.  When that
  584. happens, the tail pointer is advanced by 2, and the character and its scan
  585. code are placed into the buffer.  Each time a new key is pressed the
  586. character and scan code are added to the end of the buffer and the tail
  587. pointer is advanced by two; each time a key is read by an application the
  588. word at the current head is returned and the head pointer is advanced.
  589.    Note that the head and tail addresses assume a segment of &H40, rather
  590. than zero.  Therefore, the actual values stored range from &H1E through
  591. &H3A rather than &H41E through &H43A.  Of course, address 0000:041E is the
  592. same as address 0040:001E, and you can think of the buffer address either
  593. way.  I usually treat all of low memory as being located in segment 0,
  594. because that can often save a byte of code.  BASIC (or assembly language,
  595. for that matter) can pass the number zero by value using only three bytes,
  596. compared to the four bytes needed to pass any other number.
  597.    The program below shows how to determine the number of keys that are
  598. currently pending in the buffer, and also which one will be returned next.
  599.  
  600.  
  601. CLS
  602. PRINT "You have two seconds to press a few keys..."
  603. Pause! = TIMER
  604. WHILE Pause! + 2 > TIMER: WEND
  605.  
  606. BufferHead = PeekWord%(0, &H41A)
  607. BufferTail = PeekWord%(0, &H41C)
  608.  
  609. NumKeys = (BufferTail - BufferHead) \ 2
  610. IF NumKeys < 0 THEN NumKeys = NumKeys + 16
  611. PRINT "There are"; NumKeys; "keys pending in the buffer."
  612.  
  613. PRINT "The next key waiting to be read is ";
  614. NextKey = PeekWord%(&H40, BufferHead)
  615. IF NextKey AND &HFF THEN
  616.   PRINT CHR$(34); CHR$(NextKey AND &HFF); CHR$(34)
  617. ELSE
  618.   PRINT "Extended key scan code"; NextKey \ 256
  619. END IF
  620.  
  621.  
  622. This program starts by waiting two seconds giving you a chance to press a
  623. few keys.  It then reads the buffer head and tail pointers, and from that
  624. calculates the number of keys that are pending in the buffer.  With a
  625. circular buffer the head address may be higher the tail address, so a
  626. separate test is needed to account for that.
  627.    Next, the word at the head of the buffer is retrieved, which indicates
  628. the next available key.  Since the head and tail pointers assume segment
  629. &H40, I used that instead of segment 0. PeekWord%(0, &H41E) produces less
  630. code than PeekWord%(&H40, &H1E); however, PeekWord%(0, &H400 + BufferHead)
  631. is worse than PeekWord%(&H40, BufferHead) because of the addition needed.
  632.    Data in the keyboard buffer is always a full word, and it is up to you
  633. to determine if it is a normal ASCII key or an extended key's scan code. 
  634. A normal key is indicated with a non-zero low byte, and the high byte then
  635. holds the physical hardware scan code which can usually be ignored.  If the
  636. low byte instead holds a value of zero, it is an extended key and the scan
  637. code in the high byte indicates which one.  Therefore, the BASIC statement
  638. NextKey AND &HFF masks the high byte, to test if the low byte is non-zero.
  639.    If the key is extended, then NextKey \ 256 returns the value in the high
  640. byte.  This is similar to the earlier examples that shifted bits to the
  641. right by dividing.  Unlike the earlier tests that examined only some of the
  642. bits in the equipment flag, we are interested in all of the bits in the
  643. upper byte.  Dividing by 256 copies the upper byte to the lower byte, thus
  644. discarding the lower byte entirely.
  645.    You should also refer back to the StuffBuffer program shown in Chapter
  646. 6, which accesses the keyboard buffer directly and inserts new keystrokes.
  647.  
  648.  
  649. DISKETTE DATA
  650. =============
  651.  
  652. There are several bytes in low memory that relate to the floppy and fixed
  653. disks in your PC, but most of them are best left alone.  One exception,
  654. however, is the diskette drive motor timeout duration.  Whenever a diskette
  655. drive is accessed, DOS first turns on the motor, and then waits a second
  656. or two until the motor has come up to speed.  Once DOS is certain that the
  657. disk speed is correct, reading and writing are allowed.
  658.    Because of the time it takes the diskette to become ready, DOS also
  659. keeps the motor running for two more seconds after a read or write has been
  660. completed.  This way, if another request comes along within that time,
  661. further delays can be avoided because the motor is already running.  If you
  662. know that the data your program is accessing is on a floppy disk and there
  663. may be pauses in the reading or writing, you can force the motor to stay
  664. on longer than the normal two seconds.
  665.    The byte at address &H440 controls the motor hold time, and its value
  666. is decremented at every system timer tick [every 1/18th second].  When DOS
  667. has finished accessing a diskette, it places a value into this memory
  668. location.  And when the value is decremented to zero the motor is turned
  669. off.  The current motor on/off state is reflected by the byte at address
  670. &H43F.  The program that follows shows how you can modify the timeout value
  671. by poking a new, higher value into address &H440 immediately after a
  672. command that accesses the disk.
  673.  
  674.  
  675. PRINT "Place a diskette in drive A and press a key ";
  676. WHILE INKEY$ = "": WEND
  677. FILES "A:*.*"   'this starts the motor
  678.  
  679. DEF SEG = 0
  680. POKE &H440, 91  'force drive motor on for five seconds
  681.  
  682. DO
  683.   LOCATE 10, 1, 0
  684.   PRINT PEEK(&H43F),
  685.   PRINT PEEK(&H440)
  686. LOOP WHILE PEEK(&H440)
  687.  
  688. BEEP            'watch the diskette light go out when you hear the beep
  689.  
  690.  
  691. The value you store at address &H440 is the number of timer ticks that are
  692. to elapse before the motor is turned off.  Since a new timer tick occurs
  693. every 18.2 seconds, you will multiply the number of seconds times this
  694. value using Value% = Seconds * 18.2.
  695.  
  696.  
  697. DISPLAY ADAPTER DATA
  698. ====================
  699.  
  700. As with the diskette data area, a lot of information is available that
  701. pertains to the video display, and most of it is of little use in an
  702. application programming context.  Therefore, I will discuss only some of
  703. this data.
  704.    The byte at address &H449 holds the current video mode.  Unfortunately,
  705. there is no easy way to relate the information in this byte to the current
  706. BASIC SCREEN setting.  Table 10-2 shows all of the possible values that
  707. might be present.
  708.  
  709.  
  710. Video Mode       Description
  711. ==========       =========================================
  712.      0           40 by 25 16-color text
  713.      1           40 by 25 16-color text, with color burst
  714.      2           80 by 25 16-color text
  715.      3           80 by 25 16-color text, with color burst
  716.      4           320 by 200 pixels 4-color graphics
  717.      5           320 by 200 pixels 4-color
  718.      6           640 by 200 pixels 2-color
  719.      7           80 by 25 monochrome text
  720.     13           320 by 200 pixels 16-color graphics
  721.     14           640 by 200 pixels 16-color graphics
  722.     15           640 by 350 pixels monochrome EGA graphics
  723.     16           640 by 350 pixels 16-color graphics
  724.     17           640 by 480 pixels 2-color graphics
  725.     18           640 by 480 pixels 16-color graphics
  726.     19           320 by 200 pixels 256-color graphics
  727.  
  728. Table 10-2: The video mode value at Hex address 0000:0449
  729.  
  730.  
  731. Since you will always have set the video mode yourself with a SCREEN
  732. statement, there is little reason to have to read the current mode
  733. manually.
  734.    The word at address &H44A tells how many columns are on the display, and
  735. the word at address &H44C holds the total size of the screen in bytes.  In
  736. a normal 80 column by 25 line screen mode, the value at address &H44C will
  737. be 4096, even though the screen can hold only 4000 characters.
  738.    The byte at address &H462 holds the current video page number, starting
  739. at page 0.  Please understand that BASIC lets you set pages individually
  740. for writing to and displaying, and the page reported here is that which is
  741. visible on the monitor.
  742.    We have already looked at the data at address &H463, which holds the CRT
  743. controller port address.  Although this address is a full word, only the
  744. lower byte needs to be examined to know the type of display that is active. 
  745. If the byte value at address &H463 is &HB4, then a monochrome monitor is
  746. connected and being used.  If a color adapter is active the value at this
  747. byte will instead be &HD4.
  748.  
  749.  
  750. SYSTEM TIMER DATA
  751. =================
  752.  
  753. Every 18th second the BIOS timer generates an interrupt that increments
  754. the master system timer count at address &H46C.  This counter is stored as
  755. a four-byte long integer; the count is initialized to zero at midnight, and
  756. increases to a value of just over one 1.5 million at 11:59:59 pm.
  757.    In some cases using the BIOS timer count directly can help to reduce the
  758. size of your programs, because BASIC's TIMER requires floating point math. 
  759. Chapter 9 discussed some of the issue involved in benchmarking a program,
  760. and the examples there used TIMER to know when a new 1/18th second period
  761. has just started and how long a sequence of commands took.  The following
  762. short program times a long integer assignment within a FOR/NEXT loop, and
  763. it uses the PeekWord function to access the BIOS timer count directly.
  764.  
  765.  
  766. Synch = PeekWord%(0, &H46C)
  767. DO
  768.   Start = PeekWord%(0, &H46C) 
  769. LOOP WHILE Synch = Start 
  770.  
  771. FOR X& = 1 TO 70000 
  772.   Y& = X& 
  773. NEXT 
  774.  
  775. Done = PeekWord%(0, &H46C) 
  776. PRINT Done - Start; "timer ticks have elapsed" 
  777.  
  778.  
  779. Note that it is possible for this program to report an incorrect elapsed
  780. time, since it considers only the lower of the two timer words.  If the
  781. count exceeded 65,535 during the course of the timing, the lower word will
  782. have wrapped around to a value of zero.  An enhancement to this technique
  783. would therefore be to create a PeekLong% function that returns the entire
  784. four bytes in one operation.  You could write such a function in assembly
  785. language, or use BASIC like this:
  786.  
  787.  
  788. FUNCTION PeekLong& (Segment%, Address%) STATIC
  789.   PeekLong& = PeekWord%(Segment%, Address%) + 65536 * _
  790.     PeekWord%(Segment%, Address% + 2)
  791. END FUNCTION
  792.  
  793.  
  794. Here, the PeekWord function is used to do most of the work, and the two
  795. words are combined into a single long integer.  When many timing operations
  796. are needed using these functions can increase the speed of your programs,
  797. as well as help to avoid the inclusion of the floating point math library
  798. routines.
  799.  
  800.  
  801. PRINTER TIMEOUT DATA
  802. ====================
  803.  
  804. Whenever data is sent to a parallel printer it is routed through a BIOS
  805. service that handles the actual communications with the printer hardware. 
  806. If the printer is turned off or disconnected, the BIOS can detect that
  807. immediately, and report the error to the calling program.  But when the
  808. printer is turned on but deselected (off-line) or if it has run out of
  809. paper, the BIOS waits for a certain period of time before returning with
  810. an error condition.  This gives the operator a chance to fix the problem.
  811.    The amount of time the BIOS waits varies from PC to PC, and even between
  812. different models of the same brand.  The original IBM PC waited for only
  813. a very short time, and would occasionally report an error incorrectly when
  814. used with very slow printers.  Modern PCs wait as long as two minutes
  815. before timing out, which is more than enough time to reload a new ream of
  816. paper.  Unfortunately, if you want to test if a printer is ready before
  817. using it, your program may appear to hang if the printer is disabled.
  818.    Although BASIC provides ON ERROR to trap for printer errors, many
  819. programmers prefer to avoid ON ERROR because it makes the program larger
  820. and run more slowly.  Also, ON ERROR cannot avoid the long wait the BIOS
  821. imposes.  There are several solutions to this problem.
  822.    One is to print a flashing message at the bottom of the screen that says
  823. something like, "Turn on the printer!" immediately before printing, and
  824. then clear the message afterward:
  825.  
  826.    LOCATE 25, 1
  827.    COLOR 23
  828.    PRINT "Turn on the printer!";
  829.    LPRINT Some$
  830.    COLOR 7
  831.    PRINT SPC(20)
  832.  
  833. If the printer is in fact on line and ready, the message will be displayed
  834. and cleared so quickly that it is not likely to be noticed.  Otherwise, the
  835. operator will see the message and take the appropriate action.
  836.    This technique can be enhanced to instead test the printer, before
  837. sending any data.  The most reliable way I have found to test a printer is
  838. to first send it a CHR$(32) space character, and if that is accepted print
  839. a CHR$(8) backspace to cancel the original space.  A further enhancement
  840. alters the BIOS printer timeout values stored beginning at address &H478. 
  841. The combined demonstration and function that follows performs this service
  842. using CALL Interrupt to circumvent BASIC's normal error handling routine.
  843.  
  844. DEFINT A-Z
  845. DECLARE SUB INTERRUPT (IntNo, InRegs AS ANY, OutRegs AS ANY)
  846. DECLARE FUNCTION LPTReady% (LPTNumber)
  847.  
  848. '$INCLUDE: 'REGTYPE.BI'
  849.  
  850. LPTNumber = 1
  851.  
  852. IF LPTReady%(LPTNumber) THEN
  853.   PRINT "The printer is on-line and ready to go."
  854. ELSE
  855.   PRINT "Sorry, the printer is not available."
  856. END IF
  857. END
  858.  
  859. FUNCTION LPTReady% (LPTNumber) STATIC
  860.  
  861.   DIM Regs AS RegType                'for CALL INTERRUPT
  862.   LPTReady% = 0                      'assume not ready
  863.  
  864.   Address = &H477 + LPTNumber        'LPT timeout address
  865.   DEF SEG = 0                        'access segment zero
  866.   OldValue = PEEK(Address)           'save current setting
  867.   POKE Address, 1                    '1 retry
  868.  
  869.   Regs.AX = 32                       'first print a space
  870.   Regs.DX = LPTNumber - 1            'convert to 0-based
  871.   CALL INTERRUPT(&H17, Regs, Regs)   'print the space
  872.  
  873.   Result = (Regs.AX \ 256) OR 128    'get AH, ignore busy
  874.   Result = Result AND 191            'and acknowledge
  875.   IF Result = 144 THEN               'it worked!
  876.     Regs.AX = 8                      'print a backspace
  877.     CALL INTERRUPT(&H17, Regs, Regs) '  to undo CHR$(32)
  878.     LPTReady% = -1                   'return success
  879.   END IF
  880.  
  881.   POKE Address, OldValue             'restore original
  882.                                      '  timeout value
  883. END FUNCTION
  884.  
  885. There are several important points worth mentioning here.  First, you must
  886. never use zero for the printer timeout value, or the timeout will be a *lot*
  887. longer than you anticipated.  A value of zero tells the BIOS to continue
  888. trying indefinitely, and is equivalent to using the DOS MODE LPT1: command
  889. with the ",p" argument.
  890.    Another point is that you should not use this function many times in a
  891. row, without ever printing anything.  All modern printers provide a buffer,
  892. which accepts characters as fast as the computer can send them.  If the
  893. buffer fills with spaces and backspaces before any printable characters are
  894. sent, it may be impossible to clear the buffer.  Therefore, you should
  895. perform the printer test only once or twice, just before you actually need
  896. to begin printing.
  897.  
  898.  
  899. EGA AND VGA DATA
  900. ================
  901.  
  902. The seven bytes starting at address &H484 hold information about an
  903. installed EGA or VGA display adapter.  This data should not be relied upon
  904. until you have determined that the adapter is in fact an EGA or VGA.  The
  905. Monitor function shown in Chapter 6 can be used for this.
  906.    The first byte holds the number of rows currently displayed on the
  907. screen.  The next word at addresses &H485 and &H486 tells how high each
  908. character is in scan lines.  For a normal 80 by 25 line screen this value
  909. will be 16.  After using WIDTH , 43 or WIDTH , 50 the height of each
  910. character is 8 scan lines.  Notice that this value also includes the
  911. spacing between each line.  Curiously, two bytes are set aside to hold this
  912. value, even though it is extremely unlikely that any video mode would ever
  913. require a number larger than 255.
  914.    The only other information you are likely to find useful in this data
  915. area is the amount of installed memory on the EGA or VGA adapter card. 
  916. Bits 5 and 6 at address &H487 hold the number of 64K banks, and the code
  917. that follows shows how to turn this into a meaningful number:
  918.  
  919.    DEF SEG = 0             'look in segment zero
  920.    Byte = PEEK(&H487)      'get the byte
  921.    Byte = Byte AND 96      'keep what we need (96 = 1100000b)
  922.    Byte = Byte \ 32        'shift the bits right five places
  923.    Byte = (Byte + 1) * 64  'add 1 because 0 means 64K
  924.    PRINT "This EGA/VGA adapter has"; Byte; "K memory"
  925.  
  926. After reading the EGA Features byte (listed earlier in Figure 10-1), the
  927. statement Byte = Byte AND 96 masks off all of the bits that are irrelevant. 
  928. Byte is then divided by 32 to slide those bits into the lowest position. 
  929. The number that results is coded such that 0 means 64K of installed video
  930. memory, 1 means 128K, 2 means 192K (which is never really possible), and
  931. 3 indicates 256K.  Because this value is zero-based, 1 is added to Byte
  932. before multiplying by 64.
  933.  
  934.  
  935. MISCELLANEOUS DATA
  936. ==================
  937.  
  938. The 16-byte data area that begins at address &H4F0 is called the inter-
  939. application communications area, and it is available for any arbitrary use
  940. by a program.  One possibility is for passing just a few parameters between
  941. separate programs, instead of having to use COMMON and CHAIN.  Although
  942. this data area has been available since the original IBM PC was introduced,
  943. there is a risk involved with using it because it is possible that another
  944. program or TSR has stored information there.  Chapter 9 described using the
  945. last 96 bytes in the display adapter's memory, which is both a larger
  946. buffer and is probably safer to use.
  947.    The byte at address &H500 is used as a flag by the BIOS Print Screen
  948. service to detect when it is busy.  When you press Shift-PrtSc, the BIOS
  949. routine that handles that key sets this byte to a value of 1 before
  950. beginning to print the screen.  This way if you press Shift-PrtSc again
  951. before it has finished printing, the second request can be ignored.  When
  952. the printing has completed the flag is then reset to zero.
  953.    You can set this flag manually to disable the action of the PrtSc key,
  954. and then reenable it again later:
  955.  
  956.    DEF SEG = 0
  957.    POKE &H500, 1
  958.     .
  959.     .
  960.    POKE &H500, 0
  961.  
  962. In fact, you must be *sure* to reenable PrtSc before ending your program if
  963. you have disabled it.  Otherwise, that key will be disabled until the PC
  964. is rebooted.
  965.    The last low memory address I'll describe is also one of the most
  966. potentially useful.  For systems that have only one diskette drive, the
  967. byte at address &H504 tells which drive (A or B) is currently active.  In
  968. this case, that drive serves as both A and B.  Most PC users are familiar
  969. with DOS' infamous "Insert disk for drive B" message.  This message is
  970. displayed whenever you attempt to access one of the logical drives while
  971. the other is currently active.
  972.    The problem is that this message will ruin an otherwise attractive
  973. screen design, and you have no control over where or if the message is
  974. displayed.  Fortunately, you can determine if only one drive is available,
  975. and also which is currently active.  Even better, you can set this byte to
  976. reflect either drive, and thus avoid the intervention by DOS.
  977.    If the byte at address &H504 is currently zero, then drive A is active;
  978. a value of 1 indicates drive B.  The short complete program that follows
  979. shows how to detect which drive is current.
  980.  
  981.  
  982. DEF SEG = 0
  983. Floppies% = (PEEK(&H410) AND 192) \ 64 + 1
  984. PRINT "This PC has"; Floppies%; "floppy disk drive(s)."
  985.  
  986. IF Floppies% = 1 THEN
  987.   PRINT "The disk is now acting as drive ";
  988.   CurDrive% = PEEK(&H504)
  989.   IF CurDrive% THEN
  990.     PRINT "B"
  991.   ELSE
  992.     PRINT "A"
  993.   END IF
  994. END IF
  995.  
  996.  
  997. To change from drive A to B simply use POKE &H504, 1, assuming that the
  998. current DEF SEG value is already zero.  Likewise, to change from B to A you
  999. will use POKE &H504, 0.  Of course, you must also prompt the user to change
  1000. disks as DOS would.  But at least you can control how the prompt message
  1001. is displayed.  If you do switch drives behind DOS' back, it is up to you
  1002. to prompt the user to exchange disks as necessary, and also to ensure that
  1003. files are updated and closed correctly before each switch.
  1004.  
  1005.  
  1006. INPUT/OUTPUT PORTS
  1007. ==================
  1008.  
  1009. Besides the low memory addresses that are reserved for BIOS and DOS uses,
  1010. every PC also has a collection of Input/Output (I/O) ports.  Like memory,
  1011. ports are addressed by number, and data may be read from or to written to
  1012. them.  In truth, some ports are write-only, others may only be read, and
  1013. still others can be read and written.
  1014.    Where conventional memory is often used by the operating system to hold
  1015. flags, status words, and other values, ports are used to actually control
  1016. the hardware.  For example, port number &H3F2 controls the diskette drive
  1017. motors, and appropriate OUT commands to that port can turn the motor for
  1018. any drive on or off.
  1019.    For the most part, you should not experiment with the ports unless you
  1020. know what they are for, and which values are appropriate.  As an example,
  1021. it is possible to damage your monitor by sending incorrect values through
  1022. the display adapter controller ports.  Two useful ports I will describe
  1023. here control the PC's speaker and the keyboard.
  1024.    Although BASIC offers the SOUND and PLAY statements, using them can
  1025. quickly increase the size of a program.  Both of these commands can operate
  1026. in the background, thereby continuing to produce sound after they return
  1027. to your program.  As you can imagine, this requires a lot of code to
  1028. implement.  An informal test showed that adding a single SOUND statement
  1029. increased the program size by more than 11K.  Therefore, if you do not need
  1030. the ability to have tones play in the background, the combination
  1031. demonstration and subprogram that follows can be used in place of SOUND. 
  1032. Besides avoiding the code to plays tones as a background task, this routine
  1033. also avoids SOUND's inclusion of floating point math.
  1034.  
  1035. DEFINT A-Z
  1036. DECLARE SUB BSound (Frequency, Duration)
  1037.  
  1038. CLS
  1039.  
  1040. PRINT "Sweep sound"
  1041. FOR X = 1 TO 10
  1042.   READ Frequency
  1043.   CALL BSound(Frequency, 1)
  1044. NEXT
  1045. DATA 100, 200, 300, 400, 600, 900, 1200, 1500, 1800, 2100
  1046.  
  1047. PRINT "Press a key for more..."
  1048. WHILE INKEY$ = "": WEND
  1049.  
  1050. PRINT "Telephone"
  1051. FOR X = 1 TO 10
  1052.   CALL BSound(600, 1)
  1053.   CALL BSound(800, 1)
  1054. NEXT
  1055.  
  1056. PRINT "Press a key for more..."
  1057. WHILE INKEY$ = "": WEND
  1058.  
  1059. PRINT "Siren"
  1060. FOR X = 1 TO 2
  1061.   FOR Y = 600 TO 1000 STEP 15
  1062.     CALL BSound(Y, -1)          'negative values leave
  1063.   NEXT                          '  the speaker turned on
  1064.   FOR Y = 1000 TO 600 STEP -15
  1065.     CALL BSound(Y, -1)
  1066.   NEXT
  1067. NEXT
  1068. CALL BSound(600, 1)             'force the speaker off
  1069.  
  1070. SUB BSound (Frequency, Duration) STATIC
  1071.  
  1072.   IF Frequency < 33 THEN EXIT SUB
  1073.  
  1074.   IF NOT BeenHere THEN          'do this only once for a
  1075.     BeenHere = -1               '  smoother sound effect
  1076.     OUT &H43, 182               'initialize speaker port
  1077.   END IF
  1078.  
  1079.   Period = 1190000 \ Frequency  'convert to period
  1080.   OUT &H42, Period AND &HFF     'send it as two bytes
  1081.   OUT &H42, Period \ 256        '  in succession
  1082.  
  1083.   Speaker = INP(&H61)           'read Timer port B
  1084.   Speaker = Speaker OR 3        'set the speaker bits on
  1085.   OUT &H61, Speaker
  1086.  
  1087.   DEF SEG = 0
  1088.   FOR X = 1 TO ABS(Duration)    'for each tick specified
  1089.     ThisTime = PEEK(&H46C)      '  count changes again
  1090.     DO                          'wait until the timer
  1091.     LOOP WHILE ThisTime = PEEK(&H46C)
  1092.   NEXT
  1093.  
  1094.   IF Duration > 0 THEN          'turn off if requested
  1095.     Speaker = INP(&H61)         'read Timer port B
  1096.     Speaker = Speaker AND &HFC  'set the speaker bits off
  1097.     OUT &H61, Speaker
  1098.   END IF
  1099.  
  1100. END SUB
  1101.  
  1102. The BSound routine accepts the same frequency and duration arguments as
  1103. BASIC's SOUND statement.  Each time it is called it calculates the
  1104. appropriate period based on the incoming frequency, which is what the timer
  1105. ports expect.  (Period is the reciprocal of frequency.  Here, the period
  1106. is related to the PC's clock frequency of 1,190,000 Hz.)  BSound then turns
  1107. on the speaker, waits in a loop for the specified duration, and finally
  1108. turns off the speaker before returning.
  1109.    Two extra steps are required to create a smooth effect when BSound is
  1110. called rapidly in succession.  One is that the speaker port is initialized
  1111. only once, the very first time BSound is called.  The other step lets you
  1112. optionally leave the speaker turned on when BSound returns, to avoid the
  1113. choppiness that otherwise results with sounds like the siren effect.  To
  1114. tell BSound to leave the speaker on, use an equivalent negative value for
  1115. the Duration parameter.  Just be sure to call BSound once again with a
  1116. positive duration value, or use the same set of INP and OUT statements
  1117. that BSound uses to turn the speaker off.  This is shown in the last
  1118. demonstration that creates a siren sound.
  1119.  
  1120.  
  1121. KEYBOARD PORTS
  1122.  
  1123. There are several ports associated with the keyboard, and one is of
  1124. particular interest.  The enhanced keyboards that come with AT-class and
  1125. later computers allow you to control how quickly keystrokes are repeated
  1126. automatically.  There are actually two values--one sets the initial delay
  1127. before keys begin to repeat, and the other establishes the repeat rate. 
  1128. By sending the correct values through the keyboard port, you can control
  1129. the keyboard's "typematic" response.  The complete program that follows
  1130. shows how to do this, and Table 10-3 shows how the delay and repeat rate
  1131. values are determined.
  1132.  
  1133.    OUT &H60, &HF3          'get the keyboard's attention
  1134.    FOR D& = 1 TO 100: NEXT 'brief delay to give the hardware time to settle
  1135.    Value = 7               '1/4 second initial delay, 16 CPS
  1136.    OUT &H60, Value
  1137.  
  1138.  
  1139.  
  1140.        AT-style keyboard delay and repeat rates
  1141.        ========================================
  1142.  
  1143.      initial delay --->    0.25    0.50    0.75    1.00
  1144.                            ====    ====    ====    ====
  1145. 30 characters per second:    0      20      40      60
  1146. 16 characters per second:    7      27      47      67
  1147.  8 characters per second:    F      2F      4F      6F
  1148.  4 characters per second:   17      37      57      77
  1149.  2 characters per second:   1F      3F      5F      7F
  1150.  
  1151. NOTE: All values are shown in Hexadecimal.
  1152.  
  1153. Table 10-3: Sample values for setting the initial delay and repeat rate on
  1154. an AT-style keyboard.
  1155.  
  1156.  
  1157. Table 10-3 shows only some of the possible values that can be used. 
  1158. However, you can interpolate additional values for delay times and repeat
  1159. rates between those shown.
  1160.  
  1161.  
  1162. SUMMARY
  1163. =======
  1164.  
  1165. This chapter explained what the BIOS low memory data area is, and also
  1166. discussed many of the addresses that are useful to application programs. 
  1167. A number of practical examples were given, including useful PEEK and POKE
  1168. replacements that operate on data a word, rather than a byte, at a time. 
  1169. A simple binary conversion function was shown, to help you determine the
  1170. correct values to use with AND and OR.
  1171.    You learned how to exchange serial and parallel port addresses, and how
  1172. to access communications ports 3 and 4 which BASIC normally does not allow. 
  1173. Exchanging printer ports lets you access any printer as LPT1, perhaps to
  1174. avoid having to rewrite a large program that relies on existing LPRINT
  1175. statements.  Other useful printer data that can be accessed is the BIOS
  1176. timeout value, and a routine was shown for testing the printer status
  1177. without the usual delay.
  1178.    The equipment list word was described in detail, showing how to
  1179. determine the number of diskette drives and other peripherals that are
  1180. installed.  Another useful routine showed how to determine if drive A or
  1181. B is active on a one-floppy system, and also how to change the current
  1182. status of that drive.  The various keyboard status bits were also
  1183. described, and code fragments showed how to read and set the current state.
  1184.    Finally, you learned how the hardware ports are read and written using
  1185. INP and OUT commands.  One example produced sound with much less generated
  1186. code than BASIC's SOUND, and another showed how to alter the typematic rate
  1187. on enhanced (AT) keyboards.
  1188.    The next chapter explores using CALL Interrupt in great detail, using
  1189. many examples that show how to access DOS and BIOS system services.
  1190.